
#include <g-bios.h>
#include <sysconf.h>
#include <flash/nand.h>
#include <flash/nand_ids.h>
#include <flash/nand_ecc.h>
#include <core/bitops.h>
#include <core/io.h>
#include <flash/flash.h>
#include <flash/nand_cmd.h>

#define BBT_PAGE_MASK	0xffffff3f


struct Jffs2CleanMark
{
	UINT16 hMagic;
	UINT16 hNodeType;
	UINT32 nTotoalLen;
};


static struct NandOobLayout g_oobLayerout8 =
{
	.nEccCodeNum = 3,
	.piEccPos    = {0, 1, 2},
	.mFreeRegion =
	{
		{
			.nOfOffset = 3,
		 	.nOfLen    = 2
		},
		{
			.nOfOffset = 6,
		 	.nOfLen    = 2
		}
	}
};


static struct NandOobLayout g_oobLayerout16 =
{
	.nEccCodeNum = 6,
	.piEccPos    = {0, 1, 2, 3, 6, 7},
	.mFreeRegion = {{.nOfOffset = 8, .nOfLen = 8}}
};


static struct NandOobLayout g_oobLayerout64 =
{
	.nEccCodeNum = 24,
	.piEccPos =
	{
	   40, 41, 42, 43, 44, 45, 46, 47,
	   48, 49, 50, 51, 52, 53, 54, 55,
	   56, 57, 58, 59, 60, 61, 62, 63
	},
	.mFreeRegion = {{.nOfOffset = 2, .nOfLen = 38}}
};


static int  NandDoWriteOob(struct NandFlash *pNandFlash, ULONG to, struct OobOpt *ops);

static void NandWaitReady(struct NandFlash *pNandFlash);


static void NandReleaseDevice(struct NandFlash *pNandFlash)
{
	struct NandCtrl *pNandCtrl = pNandFlash->pMaster;

	pNandCtrl->SelectChip(pNandFlash, FALSE);

	pNandCtrl->eNandState = FL_READY;
}


static BYTE NandReadByte(struct NandCtrl *pNandCtrl)
{
	return readb(pNandCtrl->pReadDataAddr);
}

// fixme:
static BYTE NandReadByte16(struct NandCtrl *pNandCtrl)
{
	return (BYTE) cpu_to_le16(readw(pNandCtrl->pReadDataAddr));
}

// fixme:
static UINT16 NandReadWord(struct NandCtrl *pNandCtrl)
{
	return readw(pNandCtrl->pReadDataAddr);
}

static void NandSelChip(struct NandFlash *pNandFlash, BOOL isCE)
{
	struct NandCtrl *pNandCtrl = pNandFlash->pMaster;

	if (FALSE == isCE)
		pNandCtrl->CmdCtrl(pNandFlash, NAND_CMMD_NONE, 0 | NAND_CTRL_CHANGE);
}

static void NandWriteBuff(struct NandCtrl *pNandCtrl, const BYTE *pBuffer, int len)
{
	int i;

	for (i = 0; i < len; i++)
	{
		writeb(pBuffer[i], pNandCtrl->pWriteDataAddr);
	}
}

static void NandReadBuff(struct NandCtrl *pNandCtrl, BYTE *pBuffer, int len)
{
	int i;

	for (i = 0; i < len; i++)
		pBuffer[i] = readb(pNandCtrl->pReadDataAddr);
}

static int NandVerifyBuff(struct NandCtrl *pNandCtrl, const BYTE *pBuffer, int len)
{
	int i;

	for (i = 0; i < len; i++)
		if (pBuffer[i] != readb(pNandCtrl->pReadDataAddr))
			return -EFAULT;
	return 0;
}

static void NandWriteBuff16(struct NandCtrl *pNandCtrl, const BYTE *pBuffer, int len)
{
	int i;
	UINT16 *p;

	p = (UINT16 *)pBuffer;
	len >>= 1;

	for (i = 0; i < len; i++)
		writew(p[i], pNandCtrl->pWriteDataAddr);
}

static void NandReadBuff16(struct NandCtrl *pNandCtrl, BYTE *pBuffer, int len)
{
	int i;

	UINT16 *p = (UINT16 *) pBuffer;
	len >>= 1;

	for (i = 0; i < len; i++)
		p[i] = readw(pNandCtrl->pReadDataAddr);
}


static int NandVerifyBuff16(struct NandCtrl *pNandCtrl, const BYTE *pBuffer, int len)
{
	int i;

	UINT16 *p = (UINT16 *) pBuffer;
	len >>= 1;

	for (i = 0; i < len; i++)
		if (p[i] != readw(pNandCtrl->pReadDataAddr))
			return -EFAULT;

	return 0;
}


static int NandBlockBad(struct NandFlash *pNandFlash, ULONG ofs, int getchip)
{
	int page, chipnr, res = 0;
	struct Flash *pFlash = NANDFLASH_GET_BASE(pNandFlash);
	struct NandCtrl *pNandCtrl = pNandFlash->pMaster;

	UINT16 bad;

	page = (int)(ofs >> pFlash->nWriteShift) & pNandFlash->dwPageNumMask;

	if (getchip)
	{
		chipnr = (int)(ofs >> pFlash->nChipShift);
 
		pNandCtrl->SelectChip(pNandFlash, TRUE);
	}

	if (pNandFlash->dwFlags & NAND_BUSWIDTH_16)
	{
		pNandCtrl->Command(pNandFlash, NAND_CMMD_READOOB, pNandFlash->nBadBlkOobPos & 0xFE,
				page);
		bad = cpu_to_le16(pNandCtrl->ReadWord(pNandCtrl));
		if (pNandFlash->nBadBlkOobPos & 0x1)
			bad >>= 8;
		if ((bad & 0xFF) != 0xff)
			res = 1;
	}
	else
	{
		pNandCtrl->Command(pNandFlash, NAND_CMMD_READOOB, pNandFlash->nBadBlkOobPos, page);
		if (pNandCtrl->ReadByte(pNandCtrl) != 0xff)
			res = 1;
	}

	if (getchip)
		NandReleaseDevice(pNandFlash);

	return res;
}


static int NandCtrlBlockMarkBad(struct NandFlash *pNandFlash, ULONG ofs)
{

	BYTE pBuffer[2] = { 0, 0 };
	int block, ret;
	struct Flash *pFlash = NANDFLASH_GET_BASE(pNandFlash);

	
	block = (int)(ofs >> pNandFlash->bbt_erase_shift);
	if (pNandFlash->bbt)
		pNandFlash->bbt[block >> 2] |= 0x01 << ((block & 0x03) << 1);

	
	if (pNandFlash->dwFlags & NAND_USE_FLASH_BBT)
		ret = NandUpdateBbt(pNandFlash, ofs);
	else
	{
 		ofs += pFlash->nOobSize;
		pNandFlash->ops.dwOpLen = pNandFlash->ops.dwOpOobLen = 2;
		pNandFlash->ops.pOpDatBuf = NULL;
		pNandFlash->ops.pOpOobBuf = pBuffer;
		pNandFlash->ops.dwOpOobOff = pNandFlash->nBadBlkOobPos & ~0x01;

		ret = NandDoWriteOob(pNandFlash, ofs, &pNandFlash->ops);
		NandReleaseDevice(pNandFlash);
	}

	if (!ret)
		pFlash->mEccStats.badblocks++;

	return ret;
}


static int NandCheckWriteProt(struct NandFlash *pNandFlash)
{
	BYTE status;
	struct NandCtrl *pNandCtrl = pNandFlash->pMaster;

	pNandCtrl->Command(pNandFlash, NAND_CMMD_STATUS, -1, -1);

	status = pNandCtrl->ReadByte(pNandCtrl);

	return (status & NAND_STATUS_WP) ? 0 : 1;
}


static int NandCheckBlockBad(struct NandFlash *pNandFlash, ULONG ofs, int getchip)
{
	struct NandCtrl *pNandCtrl = pNandFlash->pMaster;

	if (!pNandFlash->bbt)
		return pNandCtrl->BlockBad(pNandFlash, ofs, getchip);

	
	return NandIsBadBbt(pNandFlash, ofs);
}


void NandWaitReady(struct NandFlash *pNandFlash)
{
	struct NandCtrl *pNandCtrl = pNandFlash->pMaster;

	// fixme
	do {
		if (pNandCtrl->FlashIsReady(pNandFlash))
			break;
	} while (1);
}


static void NandCommand(struct NandFlash *pNandFlash, 
							unsigned int command,
							int nPageOffset, 
							int nPageBase
							)
{
	int nCtrl = NAND_CTRL_CLE | NAND_CTRL_CHANGE;
	struct Flash    *pFlash = NANDFLASH_GET_BASE(pNandFlash);
	struct NandCtrl *pNandCtrl = pNandFlash->pMaster;


	if (command == NAND_CMMD_SEQIN)
	{
		int readcmd;

		if (nPageOffset >= pFlash->nWriteSize)
		{
			nPageOffset -= pFlash->nWriteSize;
			readcmd = NAND_CMMD_READOOB;
		}
		else if (nPageOffset < 256) 
		{
			readcmd = NAND_CMMD_READ0;
		}
		else
		{
			nPageOffset -= 256;
			readcmd = NAND_CMMD_READ1;
		}

		pNandCtrl->CmdCtrl(pNandFlash, readcmd, nCtrl);

		nCtrl &= ~NAND_CTRL_CHANGE;
	}

	pNandCtrl->CmdCtrl(pNandFlash, command, nCtrl);


	nCtrl = NAND_CTRL_ALE | NAND_CTRL_CHANGE;

	if (nPageOffset != -1) 
	{
		if (pNandFlash->dwFlags & NAND_BUSWIDTH_16)
			nPageOffset >>= 1;

		pNandCtrl->CmdCtrl(pNandFlash, nPageOffset, nCtrl);

		nCtrl &= ~NAND_CTRL_CHANGE;
	}

	if (nPageBase != -1)
	{
		pNandCtrl->CmdCtrl(pNandFlash, nPageBase, nCtrl);

		nCtrl &= ~NAND_CTRL_CHANGE;
		pNandCtrl->CmdCtrl(pNandFlash, nPageBase >> 8, nCtrl);

		if (pFlash->nChipSize > (32 << 20))
			pNandCtrl->CmdCtrl(pNandFlash, nPageBase >> 16, nCtrl);
	}

	pNandCtrl->CmdCtrl(pNandFlash, NAND_CMMD_NONE, NAND_NCE | NAND_CTRL_CHANGE);

	switch (command) 
	{
	case NAND_CMMD_PAGEPROG:
	case NAND_CMMD_ERASE1:
	case NAND_CMMD_ERASE2:
	case NAND_CMMD_SEQIN:
	case NAND_CMMD_STATUS:
		return;

	case NAND_CMMD_RESET:
		if (pNandCtrl->FlashIsReady)
			break;

		udelay(pNandCtrl->nChipDelay);

		pNandCtrl->CmdCtrl(pNandFlash,
						NAND_CMMD_STATUS,
						NAND_CTRL_CLE | NAND_CTRL_CHANGE
						);

		pNandCtrl->CmdCtrl(pNandFlash,
						NAND_CMMD_NONE,
						NAND_NCE | NAND_CTRL_CHANGE
						);

		while (!(pNandCtrl->ReadByte(pNandCtrl) & NAND_STATUS_READY));

		return;

	default:
		if (!pNandCtrl->FlashIsReady)
		{
			udelay(pNandCtrl->nChipDelay);
			return;
		}

		break;
	}

	// fixme
	udelay(pNandCtrl->nChipDelay);

	NandWaitReady(pNandFlash);
}


static void NandCommandLp(struct NandFlash *pNandFlash, 
								unsigned int command,
								int nPageOffset, 
								int nPageBase
								)
{
	struct Flash *pFlash = NANDFLASH_GET_BASE(pNandFlash);
	struct NandCtrl *pNandCtrl = pNandFlash->pMaster;


	if (command == NAND_CMMD_READOOB)
	{
		nPageOffset += pFlash->nWriteSize;
		command = NAND_CMMD_READ0;
	}

	pNandCtrl->CmdCtrl(pNandFlash,
					command & 0xff,
					NAND_NCE | NAND_CLE | NAND_CTRL_CHANGE
					);

	if (nPageOffset != -1 || nPageBase != -1)
	{
		int nCtrl = NAND_CTRL_CHANGE | NAND_NCE | NAND_ALE;


		if (nPageOffset != -1)
		{
			if (pNandFlash->dwFlags & NAND_BUSWIDTH_16)
				nPageOffset >>= 1;

			pNandCtrl->CmdCtrl(pNandFlash, nPageOffset, nCtrl);
			nCtrl &= ~NAND_CTRL_CHANGE;
			pNandCtrl->CmdCtrl(pNandFlash, nPageOffset >> 8, nCtrl);
		}

		if (nPageBase != -1)
		{
			pNandCtrl->CmdCtrl(pNandFlash, nPageBase, nCtrl);
			pNandCtrl->CmdCtrl(pNandFlash, nPageBase >> 8, NAND_NCE | NAND_ALE);
			
			if (pFlash->nChipSize > (128 << 20))
				pNandCtrl->CmdCtrl(pNandFlash, nPageBase >> 16, NAND_NCE | NAND_ALE);
		}
	}

	pNandCtrl->CmdCtrl(pNandFlash, 
					NAND_CMMD_NONE,
					NAND_NCE | NAND_CTRL_CHANGE
					);

	switch (command)
	{
	case NAND_CMMD_CACHEDPROG:
	case NAND_CMMD_PAGEPROG:
	case NAND_CMMD_ERASE1:
	case NAND_CMMD_ERASE2:
	case NAND_CMMD_SEQIN:
	case NAND_CMMD_RNDIN:
	case NAND_CMMD_STATUS:
	case NAND_CMMD_DEPLETE1:
		return;

	case NAND_CMMD_STATUS_ERROR:
	case NAND_CMMD_STATUS_ERROR0:
	case NAND_CMMD_STATUS_ERROR1:
	case NAND_CMMD_STATUS_ERROR2:
	case NAND_CMMD_STATUS_ERROR3:
		udelay(pNandCtrl->nChipDelay);
		return;

	case NAND_CMMD_RESET:
		if (pNandCtrl->FlashIsReady)
			break;

		udelay(pNandCtrl->nChipDelay);

		pNandCtrl->CmdCtrl(pNandFlash, 
						NAND_CMMD_STATUS,
						NAND_NCE | NAND_CLE | NAND_CTRL_CHANGE
						);

		pNandCtrl->CmdCtrl(pNandFlash, 
						NAND_CMMD_NONE,
						NAND_NCE | NAND_CTRL_CHANGE
						);

		while (!(pNandCtrl->ReadByte(pNandCtrl) & NAND_STATUS_READY));

		return;

	case NAND_CMMD_RNDOUT:		
		pNandCtrl->CmdCtrl(pNandFlash,
						NAND_CMMD_RNDOUTSTART,
						NAND_NCE | NAND_CLE | NAND_CTRL_CHANGE
						);

		pNandCtrl->CmdCtrl(pNandFlash,
						NAND_CMMD_NONE,
						NAND_NCE | NAND_CTRL_CHANGE
						);
		return;

	case NAND_CMMD_READ0:
		pNandCtrl->CmdCtrl(pNandFlash,
						NAND_CMMD_READSTART,
						NAND_NCE | NAND_CLE | NAND_CTRL_CHANGE
						);

		pNandCtrl->CmdCtrl(pNandFlash,
						NAND_CMMD_NONE,
						NAND_NCE | NAND_CTRL_CHANGE
						);

	default:
		if (!pNandCtrl->FlashIsReady)
		{
			udelay(pNandCtrl->nChipDelay);
			return;
		}

		break;
	}

	ndelay(100);

	NandWaitReady(pNandFlash);
}

 
static int NandWait(struct NandFlash *pNandFlash)
{
	struct NandCtrl *pNandCtrl = pNandFlash->pMaster;
	int status, state = pNandCtrl->eNandState;

	// fixme: tWB
	ndelay(100);
	// ndelay(pNandCtrl->nChipDelay);

	if ((state == FL_ERASING) && (pNandFlash->dwFlags & NAND_IS_AND))
		pNandCtrl->Command(pNandFlash, NAND_CMMD_STATUS_MULTI, -1, -1);
	else
		pNandCtrl->Command(pNandFlash, NAND_CMMD_STATUS, -1, -1);

	while (1) 
	{
		if (pNandCtrl->FlashIsReady)
		{
			if (pNandCtrl->FlashIsReady(pNandFlash))
				break;
		}
		else
		{
			if (pNandCtrl->ReadByte(pNandCtrl) & NAND_STATUS_READY)
				break;
		}
	}

	status = (int)pNandCtrl->ReadByte(pNandCtrl);

	return status;
}

static int NandReadPageRaw(struct NandFlash *pNandFlash, BYTE *pBuffer)
{
	struct Flash *pFlash = NANDFLASH_GET_BASE(pNandFlash);
	struct NandCtrl *pNandCtrl = pNandFlash->pMaster;

	pNandCtrl->RawRead(pNandCtrl, pBuffer, pFlash->nPageSize);
	pNandCtrl->RawRead(pNandCtrl, pNandFlash->pOobBuf, pFlash->nOobSize);

	return 0;
}


static void NandWritePageSoftEcc(struct NandFlash *pNandFlash, const BYTE *pBuffer)
{
	struct Flash *pFlash = NANDFLASH_GET_BASE(pNandFlash);
	struct NandCtrl *pNandCtrl = pNandFlash->pMaster;
	int i;
	const BYTE *p;
	BYTE *pEccCodes  = pNandFlash->buffers->ecccalc;
	UINT32 *piEccPos = pNandCtrl->pCurOobLayout->piEccPos;


	i = 0;
	p = pBuffer;

	while (p < pBuffer + pFlash->nPageSize)
	{
		NandEccCalculate(pNandFlash, p, pEccCodes + i);

		i += SOFT_ECC_CODE_NUM;
		p += SOFT_ECC_DATA_LEN;
	}

	while(--i >= 0)
	{
		pNandFlash->pOobBuf[piEccPos[i]] = pEccCodes[i];
	}

	pNandCtrl->WritePageRaw(pNandFlash, pBuffer);
}

static int NandReadPageSoftEcc(struct NandFlash *pNandFlash, BYTE *pBuffer)
{
	struct Flash *pFlash = NANDFLASH_GET_BASE(pNandFlash);
	struct NandCtrl *pNandCtrl = pNandFlash->pMaster;
	int i;
	BYTE *p;


	BYTE *pEccCopDescurr  = pNandFlash->buffers->ecccalc;
	BYTE *pEccCodesSaved = pNandFlash->buffers->ecccode;
	UINT32 *piEccPos     = pNandCtrl->pCurOobLayout->piEccPos;

	pNandCtrl->ReadPageRaw(pNandFlash, pBuffer);

	i = 0;
	p = pBuffer;

	while (p < pBuffer + pFlash->nPageSize)
	{
		NandEccCalculate(pNandFlash, p, pEccCopDescurr + i);

		i += SOFT_ECC_CODE_NUM;
		p += SOFT_ECC_DATA_LEN;
	}

	while (--i >= 0)
	{
		pEccCodesSaved[i] = pNandFlash->pOobBuf[piEccPos[i]];
	}

	i = 0;
	p = pBuffer;

	while (p < pBuffer + pFlash->nPageSize)
	{
		int stat;

		stat = NandEccCorrect(pNandFlash, p, &pEccCodesSaved[i], pEccCopDescurr + i);

		if (stat < 0)
			pFlash->mEccStats.nEccFailedCount++;
		else
			pFlash->mEccStats.nEccCorrectCount += stat;

		i += SOFT_ECC_CODE_NUM;
		p += SOFT_ECC_DATA_LEN;
	}

	return 0;
}


static void NandWritePageHardEcc(struct NandFlash *pNandFlash, const BYTE *pBuffer)
{
	struct Flash *pFlash = NANDFLASH_GET_BASE(pNandFlash);
	struct NandCtrl *pNandCtrl = pNandFlash->pMaster;

	const BYTE *p;
	int i;

	int nEccDataLen = pNandCtrl->nEccDataLen;
	int nEccCodeNum = pNandCtrl->nEccCodeNum;

	BYTE *pEccCodes  = pNandFlash->buffers->ecccalc;
	UINT32 *piEccPos = pNandCtrl->pCurOobLayout->piEccPos;

	i = 0;
	p = pBuffer;

	while (p < pBuffer + pFlash->nPageSize)
	{
		pNandCtrl->EccEnable(pNandFlash, NAND_ECC_WRITE);
		pNandCtrl->RawWrite(pNandCtrl, p, nEccDataLen);
		pNandCtrl->EccGenerate(pNandFlash, p, pEccCodes + i);

		i += nEccCodeNum;
		p += nEccDataLen;
	}

	while(--i >= 0)
		pNandFlash->pOobBuf[piEccPos[i]] = pEccCodes[i];

	pNandCtrl->RawWrite(pNandCtrl, pNandFlash->pOobBuf, pFlash->nOobSize);
}


static int NandReadPageHardEcc(struct NandFlash *pNandFlash, BYTE *pBuffer)
{
	struct Flash *pFlash = NANDFLASH_GET_BASE(pNandFlash);
	struct NandCtrl *pNandCtrl = pNandFlash->pMaster;

	int i; 
	BYTE *p;

	int nEccDataLen = pNandCtrl->nEccDataLen;
	int nEccCodeNum = pNandCtrl->nEccCodeNum;

	BYTE *pEccCodesSaved = pNandFlash->buffers->ecccode;
	BYTE *pEccCopDescurr  = pNandFlash->buffers->ecccalc;
	UINT32 *piEccPos     = pNandCtrl->pCurOobLayout->piEccPos;


	i = 0;
	p = pBuffer;

	while (p < pBuffer + pFlash->nPageSize)
	{
		pNandCtrl->EccEnable(pNandFlash, NAND_ECC_READ);
		pNandCtrl->RawRead(pNandCtrl, p, nEccDataLen);
		pNandCtrl->EccGenerate(pNandFlash, p, pEccCopDescurr + i);

		i += nEccCodeNum;
		p += nEccDataLen;
	}


	pNandCtrl->RawRead(pNandCtrl, pNandFlash->pOobBuf, pFlash->nOobSize);

	while(--i >= 0)
		pEccCodesSaved[i] = pNandFlash->pOobBuf[piEccPos[i]];


	i = 0;
	p = pBuffer;

	while (p < pBuffer + pFlash->nPageSize)
	{
		int stat;

		stat = pNandCtrl->EccCorrect(pNandFlash, p, &pEccCodesSaved[i], pEccCopDescurr + i);

		if (stat < 0)
			pFlash->mEccStats.nEccFailedCount++;
		else
			pFlash->mEccStats.nEccCorrectCount += stat;

		i += nEccCodeNum;
		p += nEccDataLen;
	}

	return 0;
}


static BYTE *NandTransOob(struct NandFlash *pNandFlash,
							BYTE *oob,
							struct OobOpt *ops,
							ULONG len
							)
{
	struct NandCtrl *pNandCtrl = pNandFlash->pMaster;


	switch(ops->eOpMode)
	{
	case FLASH_OOB_PLACE:
	case FLASH_OOB_RAW:
		memcpy(oob, pNandFlash->pOobBuf + ops->dwOpOobOff, len);
		return oob + len;

	case FLASH_OOB_AUTO:
	{
		struct OobFreeRegion *free = pNandCtrl->pCurOobLayout->mFreeRegion;
		UINT32 boffs = 0, roffs = ops->dwOpOobOff;
		ULONG bytes = 0;

		for(; free->nOfLen && len; free++, len -= bytes)
		{
			
			if (roffs)
			{
				if (roffs >= free->nOfLen)
				{
					roffs -= free->nOfLen;
					continue;
				}
				boffs = free->nOfOffset + roffs;
				bytes = min_t(ULONG, len,
						(free->nOfLen - roffs));
				roffs = 0;
			}
			else
			{
				bytes = min_t(ULONG, len, free->nOfLen);
				boffs = free->nOfOffset;
			}

			memcpy(oob, pNandFlash->pOobBuf + boffs, bytes);
			oob += bytes;
		}

		return oob;
	}

	default:
		BUG();
	}
	return NULL;
}


static int NandReadByOpt(struct NandFlash *pNandFlash, ULONG from, struct OobOpt *ops)
{
	int chipnr, page, realpage, aligned;
	struct Flash *pFlash = NANDFLASH_GET_BASE(pNandFlash);
	struct NandCtrl *pNandCtrl = pNandFlash->pMaster;
	struct EccStats stats;
	int blkcheck = (1 << (pNandFlash->phys_erase_shift - pFlash->nWriteShift)) - 1;
	int ret = 0;
	UINT32 readlen;
	UINT32 oobreadlen;
	BYTE *oob, *pBuffer;

	stats = pFlash->mEccStats;

	chipnr = from >> pFlash->nChipShift;
	pNandCtrl->SelectChip(pNandFlash, TRUE);

	realpage = from >> pFlash->nWriteShift;
	page = realpage & pNandFlash->dwPageNumMask;

	pBuffer = ops->pOpDatBuf;
	oob = ops->pOpOobBuf;

	readlen = 0;
	oobreadlen = 0;


	// TODO: support cache read
	while (readlen < ops->dwOpLen) 
	{
		pNandCtrl->Command(pNandFlash, NAND_CMMD_READ0, 0x00, page);

		switch (ops->eOpMode)
		{
		case FLASH_OOB_RAW:
			ret = pNandCtrl->ReadPageRaw(pNandFlash, pBuffer);

			if (ret < 0)
				goto L1;

			pBuffer = NandTransOob(pNandFlash,
								pBuffer + pFlash->nPageSize,
								ops,
								pFlash->nOobSize
								);

			readlen += pFlash->nPageSize + pFlash->nOobSize; 

			break;

		case FLASH_OOB_AUTO:
		case FLASH_OOB_PLACE:
			ret = pNandCtrl->ReadPage(pNandFlash, pBuffer);

			if (ret < 0)
				goto L1;

			pBuffer += pFlash->nPageSize;
			readlen += pFlash->nPageSize;

			if (oob)
			{
				oob = NandTransOob(pNandFlash,
								oob,
								ops,
								ops->dwOpOobLen
								);

				oobreadlen += ops->dwOpOobLen; // error! fix NandTransOob()
			}

			break;

		default:
			BUG();
		}


		if (pFlash->pHookFunc && pFlash->pHookParam)
		{
			pFlash->pHookParam->nPageIndex	= page;
			pFlash->pHookParam->nBlockIndex = page >> (pFlash->nEraseShift - pFlash->nWriteShift);

			pFlash->pHookFunc(pFlash, pFlash->pHookParam);
		}

		// here is the right place ?
		if (!pNandCtrl->FlashIsReady)
			udelay(pNandCtrl->nChipDelay);
		else
			NandWaitReady(pNandFlash);

		realpage++;
		page = realpage & pNandFlash->dwPageNumMask;
	}


L1:
	ops->dwOpRetLen = readlen;

	if (oob)
		ops->dwOpOobRetLen = oobreadlen;

	if (ret)
		return ret;

	if (pFlash->mEccStats.nEccFailedCount - stats.nEccFailedCount)
		return -EBADMSG;

	return  pFlash->mEccStats.nEccCorrectCount - stats.nEccCorrectCount ? -EUCLEAN : 0;
}


static int NandRead(struct NandFlash *pNandFlash, 
					ULONG from, 
					ULONG len,
					ULONG *retlen, 
					BYTE *pBuffer
					)
{
	int ret;
	struct Flash *pFlash = NANDFLASH_GET_BASE(pNandFlash);


	if (from + len > pFlash->nChipSize)
	{
		DPRINT("%s(): read range beyond flash chip size!\n", __FUNCTION__);
		return -EINVAL;
	}

	if (!len)
		return 0;
 
	pNandFlash->ops.dwOpLen = len;
	pNandFlash->ops.pOpDatBuf = pBuffer;
	pNandFlash->ops.pOpOobBuf = NULL;

	ret = NandReadByOpt(pNandFlash, from, &pNandFlash->ops);

	*retlen = pNandFlash->ops.dwOpRetLen;

	NandReleaseDevice(pNandFlash);

	return ret;
}



static int NandReadOobStd(struct NandFlash *pNandFlash, int page, int sndcmd)
{
	struct Flash *pFlash = NANDFLASH_GET_BASE(pNandFlash);
	struct NandCtrl *pNandCtrl = pNandFlash->pMaster;

	if (sndcmd)
	{
		pNandCtrl->Command(pNandFlash, NAND_CMMD_READOOB, 0, page);
		sndcmd = 0;
	}

	pNandCtrl->RawRead(pNandCtrl, pNandFlash->pOobBuf, pFlash->nOobSize);

	return sndcmd; // fixme
}



static int NandWriteOobStd(struct NandFlash *pNandFlash, 
				int page)
{
	struct Flash *pFlash = NANDFLASH_GET_BASE(pNandFlash);
	struct NandCtrl *pNandCtrl = pNandFlash->pMaster;

	int status = 0;
	const BYTE *pBuffer = pNandFlash->pOobBuf;
	int length = pFlash->nOobSize;


	pNandCtrl->Command(pNandFlash, NAND_CMMD_SEQIN, pFlash->nWriteSize, page);
	pNandCtrl->RawWrite(pNandCtrl, pBuffer, length);
	
	pNandCtrl->Command(pNandFlash, NAND_CMMD_PAGEPROG, -1, -1);

	status = pNandCtrl->WaitFunc(pNandFlash);

	return status & NAND_STATUS_FAIL ? -EIO : 0;
}



static int NandDoReadOob(struct NandFlash *pNandFlash,
							ULONG from,
							struct OobOpt *ops
							)
{
	int page, realpage, chipnr, sndcmd = 1;
	struct Flash *pFlash = NANDFLASH_GET_BASE(pNandFlash);
	struct NandCtrl *pNandCtrl = pNandFlash->pMaster;
	int blkcheck = (1 << (pNandFlash->phys_erase_shift - pFlash->nWriteShift)) - 1;
	int readlen = ops->dwOpOobLen;
	int len;
	BYTE *pBuffer = ops->pOpOobBuf;


	DPRINT("%s(): from = 0x%08x, len = %i\n",
		__FUNCTION__, from, readlen);

	if (ops->eOpMode == FLASH_OOB_AUTO)
		len = pNandCtrl->pCurOobLayout->nFreeOobSum;
	else
		len = pFlash->nOobSize;

	if ((ops->dwOpOobOff >= len))
	{
		DPRINT("%s(): Attempt to start read outside oob\n", __FUNCTION__);
		return -EINVAL;
	}

	// fixme
	if ((from >= pFlash->nChipSize ||
		ops->dwOpOobOff + readlen > ((pFlash->nChipSize >> pFlash->nWriteShift) -
		(from >> pFlash->nWriteShift)) * len))
	{
		DPRINT("%s(): Attempt read beyond end of device\n", __FUNCTION__);
		return -EINVAL;
	}

	chipnr = (int)(from >> pFlash->nChipShift);
	pNandCtrl->SelectChip(pNandFlash, TRUE);

	
	realpage = (int)(from >> pFlash->nWriteShift);
	page = realpage & pNandFlash->dwPageNumMask;

	while (1)
	{
		sndcmd = pNandCtrl->ReadOob(pNandFlash, page, sndcmd);

		len = min(len, readlen);
		pBuffer = NandTransOob(pNandFlash, pBuffer, ops, len);

		if (!(pNandFlash->dwFlags & NAND_NO_READRDY))
		{
			if (!pNandCtrl->FlashIsReady)
				udelay(pNandCtrl->nChipDelay);
			else
				NandWaitReady(pNandFlash);
		}

		readlen -= len;
		if (!readlen)
			break;

		if (pFlash->pHookFunc && pFlash->pHookParam)
		{
			pFlash->pHookParam->nPageIndex  = page;
			pFlash->pHookParam->nBlockIndex = page >> (pFlash->nEraseShift - pFlash->nWriteShift);

			pFlash->pHookFunc(pFlash, pFlash->pHookParam);
		}
		
		realpage++;

		page = realpage & pNandFlash->dwPageNumMask;
		
		if (!page)
		{
			chipnr++;
			pNandCtrl->SelectChip(pNandFlash, FALSE);
			pNandCtrl->SelectChip(pNandFlash, TRUE);
		}

		if (!NAND_CANAUTOINCR(pNandFlash) || !(page & blkcheck))
			sndcmd = 1;
	}

	ops->dwOpOobRetLen = ops->dwOpOobLen;

	return 0;
}


static int NandReadOob(struct NandFlash *pNandFlash, 
						ULONG from,
						struct OobOpt *ops
						)
{

	int ret = -ENOTSUPP;
	struct Flash *pFlash = NANDFLASH_GET_BASE(pNandFlash);


	ops->dwOpRetLen = 0;
	
	if (ops->pOpDatBuf && (from + ops->dwOpLen) > pFlash->nChipSize)
	{
		DPRINT("%s(): Attempt read beyond end of device\n", __FUNCTION__);
		return -EINVAL;
	}

 
	switch(ops->eOpMode)
	{
	case FLASH_OOB_PLACE:
	case FLASH_OOB_AUTO:
	case FLASH_OOB_RAW:
		break;

	default:
		goto out;
	}

	if (NULL == ops->pOpDatBuf)
		ret = NandDoReadOob(pNandFlash, from, ops);
	else
		ret = NandReadByOpt(pNandFlash, from, ops);

 out:
	NandReleaseDevice(pNandFlash);
	return ret;
}


static void NandWritePageRaw(struct NandFlash *pNandFlash, const BYTE *pBuffer)
{
	int i;
	struct Flash *pFlash = NANDFLASH_GET_BASE(pNandFlash);
	struct NandCtrl *pNandCtrl = pNandFlash->pMaster;

	pNandCtrl->RawWrite(pNandCtrl, pBuffer, pFlash->nWriteSize);

	pNandCtrl->RawWrite(pNandCtrl, pNandFlash->pOobBuf, pFlash->nOobSize);
}


static BYTE *NandFillOob(struct NandFlash *pNandFlash, BYTE *oob, struct OobOpt *ops)
{
	ULONG len = ops->dwOpOobLen;
	struct NandCtrl *pNandCtrl = pNandFlash->pMaster;


	switch(ops->eOpMode)
	{
	case FLASH_OOB_PLACE:
	case FLASH_OOB_RAW:
		memcpy(pNandFlash->pOobBuf + ops->dwOpOobOff, oob, len);
		return oob + len;

	case FLASH_OOB_AUTO:
	{
		struct OobFreeRegion *free = pNandCtrl->pCurOobLayout->mFreeRegion;
		UINT32 boffs = 0, woffs = ops->dwOpOobOff;
		ULONG bytes = 0;

		for(; free->nOfLen && len; free++, len -= bytes)
		{
			
			if (woffs) 
			{
				if (woffs >= free->nOfLen) {
					woffs -= free->nOfLen;
					continue;
				}
				boffs = free->nOfOffset + woffs;
				bytes = min_t(ULONG, len,
						(free->nOfLen - woffs));
				woffs = 0;
			}
			else
			{
				bytes = min_t(ULONG, len, free->nOfLen);
				boffs = free->nOfOffset;
			}

			memcpy(pNandFlash->pOobBuf + boffs, oob, bytes);
			oob += bytes;
		}
		return oob;
	}
	default:
		BUG();
	}
	return NULL;
}

static BOOL NandWriteNotAlligned(struct NandFlash *pNandFlash, ULONG size)
{
	return (size & (pNandFlash->parent.nWriteSize - 1)) != 0;
}

static int NandWriteByOpt(struct NandFlash *pNandFlash, long to, struct OobOpt *ops)
{
	int realpage, page, blockmask;
	struct Flash *pFlash = NANDFLASH_GET_BASE(pNandFlash);
	struct NandCtrl *pNandCtrl = pNandFlash->pMaster;

	UINT32 writelen;
	BYTE *oob = ops->pOpOobBuf;
	BYTE *pBuffer = ops->pOpDatBuf;
	int status;


	ops->dwOpRetLen = 0;
	if (!ops->dwOpLen)
		return 0;

	if (NandWriteNotAlligned(pNandFlash, to))
	{
		printf("%s(): page not aligned!\n", __FUNCTION__);
		return -EINVAL;
	}

	// TODO: check ops->dwOpLen align
#if 0
	if (FLASH_OOB_RAW != ops->eOpMode)
	{
		if (NandWriteNotAlligned(pNandFlash, ops->dwOpLen))
		{
			printf("%s(): page not aligned!\n", __FUNCTION__);
			return -EINVAL;
		}
	}
	else
	{
		if (ops->dwOpLen % (pFlash->nPageSize + pFlash->nOobSize))
		{
			printf("%s(): page not aligned!\n", __FUNCTION__);
			return -EINVAL;
		}
	}
#endif

	pNandCtrl->SelectChip(pNandFlash, TRUE);
	
	if (NandCheckWriteProt(pNandFlash))
	{
		BUG();
		return -EIO;
	}

	realpage = to >> pFlash->nWriteShift;
	page = realpage & pNandFlash->dwPageNumMask;
	blockmask = (1 << (pNandFlash->phys_erase_shift - pFlash->nWriteShift)) - 1;
	
	if (to <= (pNandFlash->nPageInBuff << pFlash->nWriteShift) &&
		(pNandFlash->nPageInBuff << pFlash->nWriteShift) < (to + ops->dwOpLen))
		pNandFlash->nPageInBuff = -1;

	if (!oob)
		memset(pNandFlash->pOobBuf, 0xff, pFlash->nOobSize);


	writelen = 0;

	while (writelen < ops->dwOpLen)
	{
		BYTE *wbuf = pBuffer;

		pBuffer  += pFlash->nPageSize;
		writelen += pFlash->nPageSize;

		pNandCtrl->Command(pNandFlash, NAND_CMMD_SEQIN, 0x00, page);

		switch (ops->eOpMode)
		{
		case FLASH_OOB_RAW:
			pBuffer  = NandFillOob(pNandFlash, pBuffer, ops);
			writelen += pFlash->nOobSize; // or ops.dwOpOobLen;

			pNandCtrl->WritePageRaw(pNandFlash, wbuf);

			break;

		case FLASH_OOB_PLACE:
		case FLASH_OOB_AUTO:
			if (oob)
				oob = NandFillOob(pNandFlash, oob, ops);

			pNandCtrl->WritePage(pNandFlash, wbuf);

			break;

		default:
			BUG();
		}

		pNandCtrl->Command(pNandFlash, NAND_CMMD_PAGEPROG, -1, -1);

		status = pNandCtrl->WaitFunc(pNandFlash);

		if (status & NAND_STATUS_FAIL)
		{
			printf("%s(): error @ line %d\n", __FUNCTION__, __LINE__);
			return -EIO;
		}


		if (pFlash->pHookFunc && pFlash->pHookParam)
		{
			pFlash->pHookParam->nPageIndex  = page;
			pFlash->pHookParam->nBlockIndex = page >> (pFlash->nEraseShift - pFlash->nWriteShift);

			pFlash->pHookFunc(pFlash, pFlash->pHookParam);
		}

		realpage++;
		page = realpage & pNandFlash->dwPageNumMask;
	}

	ops->dwOpRetLen = writelen;

	if (oob)
		ops->dwOpOobRetLen = ops->dwOpOobLen;

	return 0;
}


static int NandWrite(struct NandFlash *pNandFlash, 
					ULONG to, ULONG len,
					ULONG *retlen, 
					const BYTE *pBuffer
					)
{
	struct Flash *pFlash = NANDFLASH_GET_BASE(pNandFlash);
	int ret;

	if ((to + len) > pFlash->nChipSize)
	{
		printf("%s(): error @ line %d. range error!\n", __FUNCTION__, __LINE__);
		return -EINVAL;
	}

	if (!len)
		return 0;
 
	pNandFlash->ops.dwOpLen = len;
	pNandFlash->ops.pOpDatBuf = (BYTE *)pBuffer;
	pNandFlash->ops.pOpOobBuf = NULL;

	ret = NandWriteByOpt(pNandFlash, to, &pNandFlash->ops);

	*retlen = pNandFlash->ops.dwOpRetLen;

	NandReleaseDevice(pNandFlash);

	return ret;
}


static int NandDoWriteOob(struct NandFlash *pNandFlash, ULONG to,
				struct OobOpt *ops)
{
	int chipnr, page, status, len;
	struct Flash *pFlash = NANDFLASH_GET_BASE(pNandFlash);
	struct NandCtrl *pNandCtrl = pNandFlash->pMaster;


	DPRINT("%s(): to = 0x%08x, len = %i\n",
		__FUNCTION__,
		to,
		ops->dwOpOobLen
		);

	if (ops->eOpMode == FLASH_OOB_AUTO)
		len = pNandCtrl->pCurOobLayout->nFreeOobSum;
	else
		len = pFlash->nOobSize;

	if ((ops->dwOpOobOff + ops->dwOpOobLen) > len)
	{
		DPRINT("%s(): Attempt to write past end of page\n", __FUNCTION__);
		return -EINVAL;
	}

	if ((ops->dwOpOobOff >= len))
	{
		DPRINT("%s(): Attempt to start write outside oob\n", __FUNCTION__);
		return -EINVAL;
	}

	// fixme
	if ((to >= pFlash->nChipSize ||
		ops->dwOpOobOff + ops->dwOpOobLen >
			((pFlash->nChipSize >> pFlash->nWriteShift) -
			 (to >> pFlash->nWriteShift)) * len)) {
		DPRINT("%s(): Attempt write beyond end of device\n", __FUNCTION__);
		return -EINVAL;
	}

	chipnr = (int)(to >> pFlash->nChipShift);
	pNandCtrl->SelectChip(pNandFlash, TRUE);

	page = (int)(to >> pFlash->nWriteShift);

	pNandCtrl->Command(pNandFlash, NAND_CMMD_RESET, -1, -1);

	if (NandCheckWriteProt(pNandFlash))
		return -EROFS;

	if (page == pNandFlash->nPageInBuff)
		pNandFlash->nPageInBuff = -1;

	memset(pNandFlash->pOobBuf, 0xff, pFlash->nOobSize);
	NandFillOob(pNandFlash, ops->pOpOobBuf, ops);
	status = pNandCtrl->WriteOob(pNandFlash, page & pNandFlash->dwPageNumMask);
	memset(pNandFlash->pOobBuf, 0xff, pFlash->nOobSize);

	if (status)
		return status;

	ops->dwOpOobRetLen = ops->dwOpOobLen;

	return 0;
}


static int NandWriteOob(struct NandFlash *pNandFlash,
						ULONG to,
						struct OobOpt *ops
						)
{
	struct Flash *pFlash = NANDFLASH_GET_BASE(pNandFlash);
	int ret;

	ops->dwOpRetLen = 0;

	if (ops->pOpDatBuf && (to + ops->dwOpLen) > pFlash->nChipSize)
	{
		printf("%s(): Attempt read beyond end of device\n", __FUNCTION__);
		return -EINVAL;
	}


	switch(ops->eOpMode)
	{
	case FLASH_OOB_PLACE:
	case FLASH_OOB_AUTO:
	case FLASH_OOB_RAW:
		break;

	default:
		ret = -ENOTSUPP;
		goto out;
	}

	if (!ops->pOpDatBuf)
		ret = NandDoWriteOob(pNandFlash, to, ops);
	else
		ret = NandWriteByOpt(pNandFlash, to, ops);

 out:
	NandReleaseDevice(pNandFlash);

	return ret;
}


static void EraseOneBlock(struct NandFlash *pNandFlash, int page)
{
	struct NandCtrl *pNandCtrl = pNandFlash->pMaster;

	pNandCtrl->Command(pNandFlash, NAND_CMMD_ERASE1, -1, page);
	pNandCtrl->Command(pNandFlash, NAND_CMMD_ERASE2, -1, -1);
}


static void EraseBlockEx(struct NandFlash *pNandFlash, int page)
{
	struct NandCtrl *pNandCtrl = pNandFlash->pMaster;

	pNandCtrl->Command(pNandFlash, NAND_CMMD_ERASE1, -1, page++);
	pNandCtrl->Command(pNandFlash, NAND_CMMD_ERASE1, -1, page++);
	pNandCtrl->Command(pNandFlash, NAND_CMMD_ERASE1, -1, page++);
	pNandCtrl->Command(pNandFlash, NAND_CMMD_ERASE1, -1, page);
	pNandCtrl->Command(pNandFlash, NAND_CMMD_ERASE2, -1, -1);
}


// fixme: static
int NandErase(struct NandFlash *pNandFlash, struct FerOpt *ppDesc)
{
	int nPageIndex, nEraseCount;
	int status, nPagesPerBlock, ret, chipnr;
	struct Flash *pFlash = NANDFLASH_GET_BASE(pNandFlash);
	struct NandCtrl *pNandCtrl = pNandFlash->pMaster;

	int rewrite_bbt[NAND_MAX_CHIPS]={0};
	unsigned int bbt_masked_page = 0xffffffff;

	DPRINT("%s(): start = 0x%08x, nEraseCount = 0x%08x\n",
			__FUNCTION__,
			ppDesc->dwEraseAddr, 
			ppDesc->dwEraseLen
			);

	// fixme
	if (ppDesc->dwEraseAddr & (pFlash->nEraseSize - 1))
	{
		DPRINT("%s(): Data address not aligned! (0x%08x)\n", __FUNCTION__, ppDesc->dwEraseAddr);
		return -EINVAL;
	}

	if (ppDesc->dwEraseLen & (pFlash->nEraseSize - 1))
	{
		DPRINT("%s(): Data length not aligned! (0x%08x)\n", __FUNCTION__, ppDesc->dwEraseLen);
		return -EINVAL;
	}

	if ((ppDesc->dwEraseLen + ppDesc->dwEraseAddr) > pFlash->nChipSize)
	{
		DPRINT("%s(): Data range beyond chip size! (0x%08x > 0x%08x)\n", 
			__FUNCTION__,
			ppDesc->dwEraseLen + ppDesc->dwEraseAddr,
			pFlash->nChipSize
			);

		return -EINVAL;
	}

	ppDesc->dwFailAddr = 0xffffffff;

	nPageIndex     = ppDesc->dwEraseAddr >> pFlash->nWriteShift;
	nPagesPerBlock = pFlash->nBlockSize >> pFlash->nWriteShift;

	pNandCtrl->SelectChip(pNandFlash, TRUE);

	if (NandCheckWriteProt(pNandFlash))
	{
		ppDesc->bEraseState = FLASH_ERASE_FAILED;

		DPRINT("%s(): Flash device is write protected!\n", __FUNCTION__);
		goto erase_exit;
	}

	if (pNandFlash->dwFlags & BBT_AUTO_REFRESH && !pFlash->isBadAllow)
	{
		bbt_masked_page = pNandFlash->bbt_td->pages[chipnr] & BBT_PAGE_MASK;
	}

	nEraseCount = ppDesc->dwEraseLen;

	ppDesc->bEraseState = FLASH_ERASING;

	while (nEraseCount)
	{
		if (!ppDesc->isAllowBad)	// fixme!
		{
			if (NandCheckBlockBad(pNandFlash, nPageIndex << pFlash->nWriteShift, FALSE))
			{
				printf("%s(): try to erase a bad block at 0x%08x!\n",
					__FUNCTION__,
					nPageIndex << pFlash->nWriteShift
					);

				ppDesc->bEraseState = FLASH_ERASE_FAILED;
				goto erase_exit;
			}
		}

		if (nPageIndex <= pNandFlash->nPageInBuff && pNandFlash->nPageInBuff < nPageIndex + nPagesPerBlock)
			pNandFlash->nPageInBuff = -1;

		pNandCtrl->EraseBlock(pNandFlash, nPageIndex & pNandFlash->dwPageNumMask);	

		status = pNandCtrl->WaitFunc(pNandFlash);

		if (status & NAND_STATUS_FAIL)
		{
			ppDesc->bEraseState = FLASH_ERASE_FAILED;
			ppDesc->dwFailAddr = nPageIndex << pFlash->nWriteShift;

			printf("%s(): Failed @ page 0x%08x\n", __FUNCTION__, nPageIndex);
			// fixme!!
			goto erase_exit;
		}

		if (bbt_masked_page != 0xffffffff && (nPageIndex & BBT_PAGE_MASK) == bbt_masked_page)
		{
			rewrite_bbt[chipnr] = nPageIndex << pFlash->nWriteShift;
		}

		if (ppDesc->isForJffs2)
		{
			// fixme
			static struct Jffs2CleanMark cleanmark = 
			{
				0x1985,
				0x2003
			};

			switch (pFlash->nOobSize)
			{
			case 16:
				cleanmark.nTotoalLen = 8;
				break;

			default:
				BUG();
				break;
			};

			UINT32 offset = nPageIndex << pFlash->nWriteShift;

			pNandCtrl->Command(pNandFlash, NAND_CMMD_SEQIN, pNandFlash->parent.nPageSize + 8, nPageIndex);

			pNandCtrl->RawWrite(pNandCtrl, (BYTE *)&cleanmark, 8);

			pNandCtrl->Command(pNandFlash, NAND_CMMD_PAGEPROG, -1, -1);

			pNandCtrl->WaitFunc(pNandFlash);
		}

		if (pFlash->pHookFunc && pFlash->pHookParam)
		{
			pFlash->pHookParam->nPageIndex  = nPageIndex;
			pFlash->pHookParam->nBlockIndex = nPageIndex >> (pFlash->nEraseShift - pFlash->nWriteShift);

			pFlash->pHookFunc(pFlash, pFlash->pHookParam);
		}

		nEraseCount -= pFlash->nBlockSize;
		nPageIndex	+= nPagesPerBlock;
	}

	ppDesc->bEraseState = FLASH_ERASE_DONE;

 erase_exit:

	ret = ppDesc->bEraseState == FLASH_ERASE_DONE ? 0 : -EIO;

	NandReleaseDevice(pNandFlash);

	if (bbt_masked_page == 0xffffffff || ret)
		return ret;

	for (chipnr = 0; chipnr < pNandCtrl->nChips; chipnr++)
	{
		if (!rewrite_bbt[chipnr])
			continue;

		DPRINT("%s(): Updating bad block table (%d:0x%0x 0x%0x) ... \n", 
			__FUNCTION__,
			chipnr, 
			rewrite_bbt[chipnr],
			pNandFlash->bbt_td->pages[chipnr]
			);

		NandUpdateBbt(pNandFlash, rewrite_bbt[chipnr]);
	}

	return ret;
}


static int NandBlkIsBad(struct NandFlash *pNandFlash, ULONG offs)
{
	struct Flash *pFlash = NANDFLASH_GET_BASE(pNandFlash);

	if (offs > pFlash->nChipSize)
		return -EINVAL;

	return NandCheckBlockBad(pNandFlash, offs, 1);
}


static int NandBlockMarkBad(struct NandFlash *pNandFlash, ULONG ofs)
{

	int ret;
	struct NandCtrl *pNandCtrl = pNandFlash->pMaster;

	if ((ret = NandBlkIsBad(pNandFlash, ofs))) {
		
		if (ret > 0)
			return 0;
		return ret;
	}

	return pNandCtrl->BlockMarkBad(pNandFlash, ofs);
}

 
struct NandCtrl *GkNandCtrlNew(void)
{
	int busw = 0;
	struct NandCtrl *pNandCtrl;

	pNandCtrl = kzalloc(sizeof(struct NandCtrl), GFP_KERNEL);
	if (NULL == pNandCtrl)
	{
		return NULL;
	}
	 //pNandCtrl->dwFlags & NAND_BUSWIDTH_16;

	InitListHead(&pNandCtrl->listFlash);

	pNandCtrl->nChipDelay    = 5;

	pNandCtrl->eEccMode      = NAND_ECC_SW;

	pNandCtrl->nMaxSlaves    = 1;

	pNandCtrl->Command       = NandCommand;

	pNandCtrl->WaitFunc      = NandWait;

	pNandCtrl->SelectChip    = NandSelChip;

	pNandCtrl->ReadWord      = NandReadWord;  // fixme: remove it

	pNandCtrl->BlockBad      = NandBlockBad;

	pNandCtrl->BlockMarkBad  = NandCtrlBlockMarkBad;

	pNandCtrl->ReadByte      = busw ? NandReadByte16 : NandReadByte;

	pNandCtrl->RawWrite      = busw ? NandWriteBuff16 : NandWriteBuff;

	pNandCtrl->RawRead       = busw ? NandReadBuff16 : NandReadBuff;

	pNandCtrl->VerifyBuffer  = busw ? NandVerifyBuff16 : NandVerifyBuff;

	pNandCtrl->ScanBadBlock  = NandScanBbt;
	//
	pNandCtrl->EraseBlock    = EraseOneBlock;

	pNandCtrl->ReadPageRaw   = NandReadPageRaw;

	pNandCtrl->WritePageRaw  = NandWritePageRaw;

	pNandCtrl->ReadOob       = NandReadOobStd;

	pNandCtrl->WriteOob      = NandWriteOobStd;

	// SE
	pNandCtrl->ReadPage      = NandReadPageSoftEcc;

	pNandCtrl->WritePage     = NandWritePageSoftEcc;

	return pNandCtrl;
}


static int ProbeNandFlash(struct NandFlash *pNandFlash)
{
	int i;
	struct Flash *pFlash = NANDFLASH_GET_BASE(pNandFlash);
	struct NandCtrl *pNandCtrl = pNandFlash->pMaster;

	pNandCtrl->SelectChip(pNandFlash, TRUE);

	pNandCtrl->Command(pNandFlash, NAND_CMMD_READID, 0x00, -1);

	pNandFlash->bVendorID = pNandCtrl->ReadByte(pNandCtrl);
	pNandFlash->bDeviceID = pNandCtrl->ReadByte(pNandCtrl);

	for (i = 0; g_vNandDevID[i].name != NULL; i++)
	{
		if (pNandFlash->bDeviceID == g_vNandDevID[i].id)
		{
			pFlash->nChipSize	= g_vNandDevID[i].nChipSize << 20;
			pNandFlash->dwFlags = g_vNandDevID[i].dwFlags;

			strcpy(pFlash->szName, g_vNandDevID[i].name);

			//
			if (PAGE_SIZE_AUTODETECT != g_vNandDevID[i].nPageSize)
			{
				pFlash->nEraseSize = g_vNandDevID[i].nEraseSize;

				pFlash->nWriteSize = g_vNandDevID[i].nPageSize;

				pFlash->nOobSize   = pFlash->nWriteSize / 32;
			}
			else
			{
				UINT32 dwExtID;

				dwExtID = pNandCtrl->ReadByte(pNandCtrl);

				dwExtID = pNandCtrl->ReadByte(pNandCtrl);

				DPRINT("%s(): Extend ID of %s is 0x%08x\n", __FUNCTION__, pFlash->szName, dwExtID);
	
				pFlash->nWriteSize = 1024 << (dwExtID & 0x3);
				dwExtID >>= 2;

				pFlash->nOobSize = (8 << (dwExtID & 0x01)) * (pFlash->nWriteSize >> 9);
				dwExtID >>= 2;

				pFlash->nEraseSize = (64 * 1024) << (dwExtID & 0x03);
				dwExtID >>= 2;

				pNandFlash->dwFlags &= ~NAND_BUSWIDTH_16;
				pNandFlash->dwFlags |= (dwExtID & 0x01) ? NAND_BUSWIDTH_16 : 0;
			}

			pNandCtrl->SelectChip(pNandFlash, FALSE);

			pNandFlash->dwFlags |= NAND_NO_AUTOINCR;  // fix various modes

			return 0;
		}
	}

	pNandCtrl->SelectChip(pNandFlash, FALSE);

	return -ENODEV;
}


static int FlashToNand_Read(struct Flash *pFlash, 
								ULONG from, 
								ULONG len,
								ULONG *retlen, 
								BYTE *pBuffer
								)
{
	struct NandFlash *pNandFlash = TO_NANDFLASH(pFlash);

	return NandRead(pNandFlash, from, len, retlen, pBuffer);
}


static int FlashToNand_Write(struct Flash *pFlash, 
								ULONG to, ULONG len,
								ULONG *retlen, 
								const BYTE *pBuffer
								)
{
	struct NandFlash *pNandFlash = TO_NANDFLASH(pFlash);

	return NandWrite(pNandFlash, to, len, retlen, pBuffer);
}


static int FlashToNand_Erase(struct Flash *pFlash, struct FerOpt *instr)
{
	struct NandFlash *pNandFlash = TO_NANDFLASH(pFlash);

	return NandErase(pNandFlash, instr);
}


static int FlashToNand_ReadOOB(struct Flash *pFlash, 
									ULONG from,
									struct OobOpt *ops
									)
{
	struct NandFlash *pNandFlash = TO_NANDFLASH(pFlash);

	return NandReadOob(pNandFlash, from, ops);
}


static int FlashToNand_WriteOOB(struct Flash *pFlash,
							ULONG to,
							struct OobOpt *ops
							)
{
	struct NandFlash *pNandFlash = TO_NANDFLASH(pFlash);

	return NandWriteOob(pNandFlash, to, ops);
}


static int FlashToNand_BlockIsBad(struct Flash *pFlash, ULONG offs)
{
	struct NandFlash *pNandFlash = TO_NANDFLASH(pFlash);

	return NandBlkIsBad(pNandFlash, offs);
}


static int FlashToNand_BlockMarkBad(struct Flash *pFlash, ULONG ofs)
{
	struct NandFlash *pNandFlash = TO_NANDFLASH(pFlash);

	return NandBlockMarkBad(pNandFlash, ofs);
}


static struct NandFlash *NewNandFlash(struct NandCtrl *pMaster)
{
	struct NandFlash *pNandFlash;
	struct Flash *pFlash;

	pNandFlash = kzalloc(sizeof(struct NandFlash), GFP_KERNEL);

	if (NULL == pNandFlash)
		return NULL;

	pFlash = NANDFLASH_GET_BASE(pNandFlash);

	// pFlash->vPartTab = (struct Partition *)malloc(sizeof(*pFlash->vPartTab) * MAX_FLASH_PARTS);

	pNandFlash->pMaster = pMaster;

	ListAddTail(&pNandFlash->nodeFlash, &pMaster->listFlash);

	pFlash->nType = FLASH_NANDFLASH;
	pFlash->isBadAllow = TRUE;

	pFlash->Read  = FlashToNand_Read;
	pFlash->Write = FlashToNand_Write;
	pFlash->Erase = FlashToNand_Erase;

	pFlash->ReadOob  = FlashToNand_ReadOOB;
	pFlash->WriteOob = FlashToNand_WriteOOB;

	pFlash->BlockIsBad   = FlashToNand_BlockIsBad;
	pFlash->BlockMarkBad = FlashToNand_BlockMarkBad;

	return pNandFlash;
}

static void DeleteNandFlash(struct NandFlash *pNandFlash)
{
	struct Flash *pFlash;

	ListDelNode(&pNandFlash->nodeFlash);

	pFlash = NANDFLASH_GET_BASE(pNandFlash);

	// fixme: free all buffer.

	// free(pFlash->vPartTab);
	free(pNandFlash);

}

BOOL GkNandSetEccMode(struct NandCtrl *pNandCtrl, ECC_MODE newMode, ECC_MODE *pOldMode)
{
	int i;

	if (pNandCtrl->eEccMode == newMode)
	{
		return newMode;
	}

	printf("ECC newMode: ");

	switch (newMode)
	{
	case NAND_ECC_HW:
		if (!pNandCtrl->EccEnable || !pNandCtrl->EccGenerate || !pNandCtrl->EccCorrect)
		{
			printf("Driver seems does not support hardware ECC!\n");
			return FALSE;
		}

		pNandCtrl->ReadPage      = NandReadPageHardEcc;
		pNandCtrl->WritePage     = NandWritePageHardEcc;
		pNandCtrl->pCurOobLayout = pNandCtrl->pHardOobLayout;

		//pNandCtrl->nEccOobBytes = pNandCtrl->nEccCodeNum;  // fixme

		printf("Hardware ECC\n");

		break;

	case NAND_ECC_SW:
		pNandCtrl->ReadPage      = NandReadPageSoftEcc;
		pNandCtrl->WritePage     = NandWritePageSoftEcc;
		pNandCtrl->pCurOobLayout = pNandCtrl->pSoftOobLayout;

		printf("Software ECC\n");

		break;

	case NAND_ECC_NONE:
		pNandCtrl->ReadPage      = NandReadPageRaw;
		pNandCtrl->WritePage     = NandWritePageRaw;
		pNandCtrl->pCurOobLayout = NULL;

		printf("None\n");

		break;

	default:
		printf("Invalid(%d) !\n", pNandCtrl->eEccMode);

		BUG();

		return FALSE;
	}

	pNandCtrl->pCurOobLayout->nFreeOobSum = 0;

	for (i = 0; pNandCtrl->pCurOobLayout->mFreeRegion[i].nOfLen; i++)
	{
		pNandCtrl->pCurOobLayout->nFreeOobSum += pNandCtrl->pCurOobLayout->mFreeRegion[i].nOfLen;
	}

	*pOldMode = pNandCtrl->eEccMode;
	pNandCtrl->eEccMode = newMode;

	return TRUE;
}


BOOL GuFlashSetEccMode(struct Flash *pFlash, ECC_MODE mode, ECC_MODE *pOldMode)
{
	struct NandFlash *pNandFlash = TO_NANDFLASH(pFlash);

	return GkNandSetEccMode(pNandFlash->pMaster, mode, pOldMode);
}

int GkNandCtrlRegister(struct NandCtrl *pNandCtrl)
{
	int i, ret, busw;
	struct Flash     *pFlash;
	struct NandFlash *pNandFlash;
	char szVendName[64], szChipSize[32], szBlockSize[32];

	for (i = 0; i < pNandCtrl->nMaxSlaves; i++)
	{
		int j;
	
		pNandFlash = NewNandFlash(pNandCtrl);

		if (NULL == pNandFlash)
			return -ENOMEM;

		pNandFlash->nNandID = i;

		ret = ProbeNandFlash(pNandFlash);
		if (ret < 0)
		{
			DeleteNandFlash(pNandFlash);
			break;
		}

		pFlash = NANDFLASH_GET_BASE(pNandFlash);

		for (j = 0; g_vNandVendID[j].id; j++)
		{
			if (g_vNandVendID[j].id == pNandFlash->bVendorID)
			{
				strcpy(szVendName, g_vNandVendID[j].pchVenName);
				break;
			}
		}

		if (0 == g_vNandVendID[j].id)
			strcpy(szVendName, "Unknown vendor");

		printf("NAND-%d is detected:  "
			"Vendor ID = 0x%02x, Device ID = 0x%02x "
			"(%s %s)\n",
			i, 
			pNandFlash->bVendorID, pNandFlash->bDeviceID,
			szVendName, pFlash->szName
			);

		GuValToHrStr(pFlash->nChipSize, szChipSize);
		GuValToHrStr(pFlash->nEraseSize, szBlockSize);

		printf("Flash details:\n"
			"\tChip size  = 0x%08x (%s)\n"
			"\tBlock size = 0x%08x (%s)\n"
			"\tPage size  = 0x%08x (%d)\n"
			"\tOOB size   = 0x%08x (%d)\n",
			pFlash->nChipSize, szChipSize,
			pFlash->nEraseSize, szBlockSize,
			pFlash->nWriteSize, pFlash->nWriteSize,
			pFlash->nOobSize, pFlash->nOobSize
			);

		// fix for name

		pFlash->nChipShift = ffs(pFlash->nChipSize) - 1; //fixme: to opt ffs()

		pFlash->nEraseShift = ffs(pFlash->nEraseSize) - 1;

		pFlash->nWriteShift = ffs(pFlash->nWriteSize) - 1;

		pNandFlash->dwPageNumMask = (pFlash->nChipSize >> pFlash->nWriteShift) - 1;

		pNandFlash->bbt_erase_shift = pNandFlash->phys_erase_shift = ffs(pFlash->nEraseSize) - 1;

		pNandFlash->nBadBlkOobPos = pFlash->nWriteSize > 512 ? NAND_BBP_LARGE : NAND_BBP_SMALL;


		if ((pNandFlash->dwFlags & NAND_4PAGE_ARRAY) && EraseOneBlock == pNandCtrl->EraseBlock)
			pNandCtrl->EraseBlock = EraseBlockEx;

		if (pFlash->nWriteSize > 512 && NandCommand == pNandCtrl->Command)
			pNandCtrl->Command = NandCommandLp;

		if (!(pNandFlash->dwFlags & NAND_OWN_BUFFERS))
		{
			pNandFlash->buffers = kmalloc(sizeof(*pNandFlash->buffers), GFP_KERNEL);

			if (!pNandFlash->buffers)
				return -ENOMEM;

			pNandFlash->pOobBuf = pNandFlash->buffers->pDataBuf + pFlash->nWriteSize;
		}

		switch (pFlash->nOobSize)
		{
		case 8:
			pNandCtrl->pSoftOobLayout = &g_oobLayerout8;
			break;
		case 16:
			pNandCtrl->pSoftOobLayout = &g_oobLayerout16;
			break;
		case 64:
			pNandCtrl->pSoftOobLayout = &g_oobLayerout64;
			break;
		default:
			printf("No OOB layout defined! OOB Size = %d.\n", pFlash->nOobSize);
			BUG();
		}

		if (NAND_ECC_SW == pNandCtrl->eEccMode)
		{
			pNandCtrl->pCurOobLayout = pNandCtrl->pSoftOobLayout;
		}

		pNandCtrl->pCurOobLayout->nFreeOobSum = 0;
		
		for (j = 0; pNandCtrl->pCurOobLayout->mFreeRegion[j].nOfLen; j++)
		{
			pNandCtrl->pCurOobLayout->nFreeOobSum += pNandCtrl->pCurOobLayout->mFreeRegion[j].nOfLen;
		}

		pNandFlash->nPageInBuff = -1;

		ret = GkFlashRegister(pFlash);
		if (ret < 0)
		{
			printf("%s(): fail to register deivce!\n");
			// fixme: destroy
		}

		if (pNandFlash->dwFlags & NAND_SKIP_BBTSCAN)
			return 0;

		printf("Scanning bad blocks:\n");
		ret = pNandCtrl->ScanBadBlock(pNandFlash);
		// fixme
		if (0 == ret)
		{
			printf("done!\n");
		}
		else
		{
			printf("nEccFailedCount!\n");
		}
	}

	pNandCtrl->nChips = i;

	printf("Total: %d nand %s detected\n", pNandCtrl->nChips, pNandCtrl->nChips > 1 ? "chips" : "chip");

	pNandCtrl->eNandState = FL_READY;

	return  0;
}



// fixme: virtual function.
BOOL GuFlashGetMtdName(const struct Flash *pFlash, char szMtdName[])
{
	struct NandFlash *pNandFlash;


	BUG_IF (NULL == pFlash);

	pNandFlash = TO_NANDFLASH(pFlash);

	strcpy(szMtdName, pNandFlash->pMaster->szNcName);

	return TRUE;
}


